﻿using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using SharppiFlowEditor.models;
using SharppiFlowEditor.controllers;

namespace SharppiFlowEditor.views
{
    /*
    * A class representing a graphical view of the diagram model.
    */
    public class DocumentView : ScrollableControl
    {

        // reference to the data model
        private AppModel diagram;
        // reference to the current controller
        private DiagramController contr;
        // drawing objects
        private Pen nPen, cPen;
        private Font nFont;
        private Brush nBrush, nTextBrush, nShadowBrush;
        private float nMargin;
        // node rectangle cache
        private Hashtable rCache;
        private Document document;

        public Document GraphDocument
        {
            get { return document; }
            set {
                document = value;
                Invalidate();
                Update();
            }
        }

        public Point ViewScrollPosition
        {
            get { return ((ScrollableControl)this.Parent).AutoScrollPosition; }
        }

        private Object _selectedObject;
        public Object SelectedObject
        {
            set { _selectedObject = value; }
            get { return _selectedObject; }
        }

        private Size _viewSize;

        public Size DiagramViewSize
        {
            get { return _viewSize; }
            set
            {   _viewSize = value;
                Size relativeSize = new Size((int)(_viewSize.Width * ZoomSize), (int)(_viewSize.Height * ZoomSize));
                this.Size = relativeSize;

                if (this.Parent != null)
                {
                    ((ScrollableControl)(this.Parent)).AutoScrollMinSize = relativeSize;

                }
            }
        }

        private double _zoomSize;

        // ZoomSize may be from 0.2 to 2.0
        public double ZoomSize
        {
            get { return _zoomSize; }
            set
            {
                if (value >= 0.2 && value <= 2.0)
                {
                    _zoomSize = value;
                    if (this.Parent != null)
                    {
                        int height = _viewSize.Height;
                        int width = _viewSize.Width;
                        ((ScrollableControl)(this.Parent)).AutoScrollMinSize = new Size((int)(height * value), (int)(width * value));
                        this.Size = new Size((int)(height * value), (int)(width * value));
                    }
                }

                Invalidate();
                Update();
            }
        }


        /*
         * Create the view object for the specified diagram model.
         */
        public DocumentView(AppModel diagram)
        {
            this.diagram = diagram;
            contr = null;
            rCache = new Hashtable();
            // create observer-observable connection
            diagram.DiagramChange += new DiagramEventHandler(UpdateView);
            // create update event handler
            Paint += new PaintEventHandler(PaintView);

            // init drawing objects
            nPen = new Pen(Color.Black);
            cPen = new Pen(Color.Black);
            nFont = new Font(FontFamily.GenericSansSerif, 10);
            nBrush = new SolidBrush(Color.White);
            nTextBrush = new SolidBrush(Color.Black);
            nShadowBrush = new SolidBrush(Color.FromArgb(51, Color.Black));
            nMargin = 5;
            // other settings
            ResizeRedraw = true;
            // use double buffering
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.DoubleBuffer, true);

            ZoomSize = 1.0;
            
            this.AutoScroll = false;

            this.BackColor = Color.White;
        }

        /*
         * Get the model of this view.
         */
        public AppModel Model
        {
            get { return diagram; }
        }

        /*
         * Get and set the current controller.
         */
        public DiagramController Controller
        {
            get { return contr; }
            set
            {
                // for the old controller, remove references to the view
                if (contr != null)
                {
                    MouseDown -= new MouseEventHandler(contr.ViewMouseDown);
                    MouseMove -= new MouseEventHandler(contr.ViewMouseMove);
                    MouseUp -= new MouseEventHandler(contr.ViewMouseUp);
                }
                contr = value;
                // initialize the controller to listen to mouse events
                if (contr != null)
                {
                    MouseDown += new MouseEventHandler(contr.ViewMouseDown);
                    MouseMove += new MouseEventHandler(contr.ViewMouseMove);
                    MouseUp += new MouseEventHandler(contr.ViewMouseUp);
                }

            }
        }

        /*
         * Called when the model changes.
         */
        public void UpdateView(object sender, FlowChartEventArgs e)
        {
            // remove the cached bounds rectangle of the (possible)
            // changed node
            if (e.NodeChanged != null)
            {
                rCache.Remove(e.NodeChanged);
            }
            // it this is the last change in a series of changes
            // update the view
            if (e.LastChange)
            {
                Invalidate();
                Update();
            }
        }
        

        /*
         * Paint the contents of the view.
         */
        public void PaintView(object sender, PaintEventArgs pea)
        {
            if (GraphDocument != null)
            {
                DiagramViewSize = GraphDocument.Documentsize;
            }

            Graphics g = pea.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;

            //handle the possibility that the documentview is scrolled,
            //adjust my origin coordintates to compensate
            Point pt = AutoScrollPosition;
            g.TranslateTransform(pt.X, pt.Y);

            if (document != null)
            {
                // draw the connectors
                IEnumerator e = document.Connectors.GetEnumerator();
                while (e.MoveNext())
                {
                    Connector c = (Connector)e.Current;
                    DrawConnector(g, c);
                }
                // draw the nodes on top of the connectors
                e = document.Nodes.GetEnumerator();
                while (e.MoveNext())
                {
                    if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.ActionNode" ||
                        e.Current.GetType().ToString() == "SharppiFlowEditor.models.EndNode" ||
                        e.Current.GetType().ToString() == "SharppiFlowEditor.models.ConditionalNode")
                    {
                        AbstractNode an = (AbstractNode)e.Current;
                        an.draw(g, ZoomSize);
                    }

                }
                // let controller draw any temporary images
                if (contr != null)
                {
                    contr.DrawTempImage(g);
                }
            }
        }

        /*
         * Get and set the pen used for drawing node frame.
         * TO DO: Invalidate, and update contents!
         */
        public Pen NodePen
        {
            get { return nPen; }
            set { nPen = value; }
        }

        /*
         * Get and set the pen used for drawing connector lines.
         * TO DO: Invalidate, and update contents!
         */
        public Pen ConnectorPen
        {
            get { return cPen; }
            set { cPen = value; }
        }

        /*
         * Get and set the font used for drawing the node title.
         * TO DO: Invalidate, empty cache and update contents!
         */
        public Font NodeFont
        {
            get { return nFont; }
            set { nFont = value; }
        }

        /*
         * Get and set the brush used for drawing the node background.
         * TO DO: Invalidate, and update contents!
         */
        public Brush NodeBrush
        {
            get { return nBrush; }
            set { nBrush = value; }
        }

        /*
         * Get and set the brush used for drawing the node title.
         * TO DO: Invalidate, and update contents!
         */
        public Brush NodeTextBrush
        {
            get { return nTextBrush; }
            set { nTextBrush = value; }
        }

        /*
         * Get and set the internal margin of nodes.
         * TO DO: Invalidate, empty cache and update contents!
         */
        public float NodeMargin
        {
            get { return nMargin; }
            set { nMargin = value; }
        }


        /*
         * Draw one connector.
         */
        protected void DrawConnector(Graphics g, Connector c)
        {
            Graphics g2 = g;

            Pen p = new Pen(c.Color);

            PointF centerPoint1 = new PointF((float)((c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2) * ZoomSize),
                                            (float)((c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2) * ZoomSize));

            PointF centerPoint2 = new PointF((int)((c.Node2.getBoundingRect().X + c.Node2.getBoundingRect().Width / 2) * ZoomSize),
                                            (int)((c.Node2.getBoundingRect().Y + c.Node2.getBoundingRect().Height / 2) * ZoomSize));
            PointF connectorMiddlePoint = new PointF((Math.Max(centerPoint1.X, centerPoint2.X) - Math.Min(centerPoint1.X, centerPoint2.X)) / 2 + Math.Min(centerPoint1.X, centerPoint2.X),
                                            ((Math.Max(centerPoint1.Y, centerPoint2.Y) - Math.Min(centerPoint1.Y, centerPoint2.Y)) / 2) + Math.Min(centerPoint1.Y, centerPoint2.Y));

            g2.DrawLine(p, centerPoint1, centerPoint2);  

            g2.DrawString(c.Text, new Font("verdana", 10F), new SolidBrush(Color.Black), new PointF(
                (Math.Max(centerPoint1.X, centerPoint2.X) - Math.Min(centerPoint1.X, centerPoint2.X)) / 2 + Math.Min(centerPoint1.X, centerPoint2.X),
                ((Math.Max(centerPoint1.Y, centerPoint2.Y) - Math.Min(centerPoint1.Y, centerPoint2.Y)) / 2) + Math.Min(centerPoint1.Y, centerPoint2.Y)
                ));

            /*
             *  Konnektorien suuntakolmioiden piirtoon liittyvä toiminnallisuus
             */

            double slope = (centerPoint2.Y - centerPoint1.Y) / (centerPoint2.X - centerPoint1.X);
            float angle = (float)(Math.Atan(slope) * 180 / Math.PI);

            Console.WriteLine("Angle: " + angle);           
            Matrix m = new Matrix();           
            m.RotateAt(angle, connectorMiddlePoint);            
            g2.Transform = m;

            //apuneliö suuntakolmion pisteiden laskua varten
            Rectangle helpRect = new Rectangle((int)connectorMiddlePoint.X-5, (int)connectorMiddlePoint.Y-5, 10,10);            
            Point[] points = new Point[] {
                new Point(helpRect.Left, (helpRect.Y + helpRect.Height/2)),
                new Point(helpRect.Right, helpRect.Y),
                new Point(helpRect.Right, helpRect.Y+ helpRect.Height)
            };
            // Varmistaa, että suuntakolmio osoittaa oikeaan suuntaan
            if (centerPoint1.X <= centerPoint2.X)
	        {
                points = new Point[] {
                    new Point(helpRect.Right, (helpRect.Y + helpRect.Height/2)),
                    new Point(helpRect.Left, helpRect.Y),
                    new Point(helpRect.Left, helpRect.Y+ helpRect.Height)
                }; 
	        }
            
            g2.FillPolygon(new SolidBrush(Color.Black), points);
            g2.Transform = new Matrix();
        } 

        /*
         * Get the last node including the given coordinate point.
         */
        internal AbstractNode GetNode(float x, float y)
        {
            int relativeX = (int)(x / ZoomSize);
            int relativeY = (int)(y / ZoomSize);

            IEnumerator e = document.Nodes.GetEnumerator();
            //Node last = null;
            while (e.MoveNext())
            {
                AbstractNode an;
                if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.ActionNode")
                {   an = (ActionNode)e.Current;
                    if (an.getBoundingRect().Contains(relativeX, relativeY))
                        return an;
                }
                else if (e.Current.GetType().ToString() == "SharppiFlowEditor.models.EndNode")
	            {
		            an = (EndNode)e.Current;
                    if (an.getBoundingRect().Contains(relativeX, relativeY))
                        return an;
	            }
                else
                {
                    an = (ConditionalNode)e.Current;
                    if (an.getBoundingRect().Contains(relativeX, relativeY))
                        return an;
                }                

            }
            return null;
        }

        public Connector getConnectorAtPoint(PointF location)
        {

            PointF relativeLocation = new PointF((int)(location.X / ZoomSize), (int)(location.Y / ZoomSize));

            Connector con = null;

            
            foreach (Connector c in document.Connectors)
            {
                c.Color = Color.Black;
            }

            foreach (Connector c in document.Connectors)
            {
                Point centerPoint1 = new Point(c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2,
                                            c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2);

                PointF p1 = new PointF(c.Node1.getBoundingRect().X + c.Node1.getBoundingRect().Width / 2,
                                            c.Node1.getBoundingRect().Y + c.Node1.getBoundingRect().Height / 2);
                PointF p2 = new PointF(c.Node2.getBoundingRect().X + c.Node2.getBoundingRect().Width / 2,
                                            c.Node2.getBoundingRect().Y + c.Node2.getBoundingRect().Height / 2);

                PointF leftNode = p2;
                PointF upperNode = p2;

                if (p1.X < p2.X)
                    leftNode = p1;
                if (p1.Y > p2.Y)
                    upperNode = p1;

                Console.WriteLine("click: " + relativeLocation.X + "," + relativeLocation.Y);

                float slope = (p2.Y - p1.Y) / (p2.X - p1.X);

                //pakotetaan slope numeeriseen muotoon:
                if (float.IsNaN(slope))
                    slope = 0F;

                Console.WriteLine("slope: " + slope);

                float correspondingY = slope * (relativeLocation.X - leftNode.X) + leftNode.Y;
                float correspondingX = (relativeLocation.Y - leftNode.Y) / slope + leftNode.X;

                float distX = correspondingX - relativeLocation.X;
                float distY = correspondingY - relativeLocation.Y;

                if (distX <= 4 &&
                    distX >= -4 ||
                    distY <= 4 &&
                    distY >= -4)
                {
                    Console.WriteLine("connector!");
                    c.Color = Color.Red;
                    con = c;
                    return con;
                }
                else
                {
                    con = null;
                }
            }
            Invalidate();
            Update();

            return con;
        }

        internal void NodeDoubleClicked(MouseEventArgs e)
        {            
            AbstractNode an = GetNode(e.X, e.Y);           

        }
    }
}